home *** CD-ROM | disk | FTP | other *** search
Text File | 1995-09-28 | 15.4 KB | 685 lines | [TEXT/CWIE] |
- // COPYRIGHT 1994 A.D. Software, All rights reserved
-
- // public layer of OOFILE database
-
-
- #include <ctype.h>
-
- #include "oof2.hpp"
- #include "oof3.hpp"
- #include "oofrel.hpp"
-
- #ifdef _Macintosh
- // error reporting alert number
- // if you have not included oofmac.rsrc or have changed the number
- // of the alert you will need to fix the following
- #define kBigErrAlert 1001
-
- class macAlertMsg{
- public:
- macAlertMsg(const char*);
- private:
- int FindWordBreakBefore(int bitLen, char* msg, int &nextWordStart);
- };
- #endif
-
-
- // define static variables
- dbConnect* dbConnect::sCurrentlyConstructing;
- dbTable* dbTable::sCurrentlyConstructing;
- dbRelHalf* dbTable::sSavedRelationHalf;
- bool dbTable::sCloningWithoutSelection=false;
-
-
- void validateDatabaseState()
- {
- if (OOF_mixRelChainEndPoint::hasTransitoryLinks())
- dbConnect::raise(ostrstream()
- << "validateDatabaseState: OOF_mixRelChainEndPoint::hasTransitoryLinks !=0" << endl
- << "you have probably missed the parens off a related field" << endl
- << "or used -> in an method call like People.Visits.count()"
- );
- // this routine should be called from every operator
- // if OOF_Debug defined, so that we quickly find out that someone used a
- // relation chain expression on a field, without parens, eg:
- // People.Visits->Name;
- }
-
-
- bool skipTillDigit(istream& is)
- {
- if (is.peek()=='\n')
- return false; // early exit - empty line
-
- while (is.good()) {
- char c;
- is >> c;
- if ((c>='0') && (c<='9')) {
- is.putback(c);
- return true;
- }
- }
- return false;
- }
-
-
-
- // -------------------------------------------------------
- // O O F _ m i x R e l C h a i n E n d P o i n t
- // -------------------------------------------------------
- bool OOF_mixRelChainEndPoint::ConsumePossibleRelationChain()
- {
- if (sTransitoryRelChainLinks && sTransitoryRelChainLinks->count()) {
- mRelationChain = sTransitoryRelChainLinks;
- sTransitoryRelChainLinks = 0;
- return true;
- }
- else
- return false;
- }
-
-
- // -------------------------------------------------------
- // d b C o n n e c t
- // -------------------------------------------------------
- dbConnect::dbConnect() :
- mTables(4, 4), /* go up in chunks of 4 tables at a time */
- mRelations(0, 2) /* no relations but add 2 at a time */
- {
- sCurrentlyConstructing = this;
- }
-
-
- dbConnect::~dbConnect() {
- }
-
-
- void dbConnect::attachTable(dbTable * theTable)
- {
- mTables.append(theTable);
- theTable->mBackend = MakeTableBackend(theTable->mFields, theTable->mTableName);
- }
-
-
- dbTable *dbConnect::table(unsigned int tableNum)
- {
- return (dbTable *) mTables[tableNum];
- }
-
-
- dbTable *dbConnect::table(const char *tableName)
- {
- return (dbTable *) mTables[tableName];
- }
-
-
- void dbConnect::describe(ostream& os)
- {
- os << "Connection:\t" << mConnectionName << endl;
- mTables.describe(os);
- mRelations.describe(os);
- }
-
-
- void dbConnect::dumpData(ostream& os)
- {
- os << endl;
- unsigned int numTables = mTables.count();
- for (unsigned int i=0; i<numTables; i++)
- {
- dbTable *theTable = (dbTable *) mTables[i]; // safe downcast
- theTable->selectAll();
- os << "table:\t" << theTable->tableName() << endl;
- theTable->extract(os);
- os << endl << endl << endl;
- }
- }
-
-
- void dbConnect::generateTestData(const char* testFileName, unsigned long maxRecs)
- {
- bool gotIt = false;
- char *buf = 0;
- int bufLen;
- // this is really silly, but I've got to do non-portable stuff
- // to get the length of the file, and read it into memory!
-
- ifstream fs(testFileName);
- if (fs) {
- // bufLen = filelength(fd);
- // bufLen = (bufLen > 10240) ? 10240 : bufLen; // max 10kb
- bufLen = 10240;
- buf = new char[bufLen];
- assert(buf);
- fs.read(buf, bufLen);
- gotIt = (fs.gcount()==bufLen);
- fs.close();
- }
- #ifdef oldSymantec_macintosh
- ifstream src(testFileName);
- if (src) {
- strstream inMemory;
- inMemory << src.rdbuf();
- src.close();
- buf = inMemory.str();
- bufLen = inMemory.rdbuf()->pcount();
- gotIt = true;
- }
- #endif
- if (gotIt) {
- unsigned int numTables = mTables.count();
- for (unsigned int i=0; i<numTables; i++)
- {
- unsigned long maxRecsForTable = maxRecs ? maxRecs : rand()%5000; // specified or random up to 5,000
- dbTable *theTable = (dbTable *) mTables[i]; // safe downcast
- theTable->generateTestData(maxRecsForTable, buf, bufLen); // up to 5000 recs
- }
-
- delete[] buf;
- }
- }
-
-
- bool dbConnect::fileExists(const char * fName)
- {
- FILE *fp;
-
- fp = fopen(fName, "rb" );
- if (fp) {
- fclose( fp );
- return true;
- }
- else
- return false;
- }
-
-
- void dbConnect::raise(ostream& os)
- {
- #ifdef _Windows
- strstream oss;
- oss << os.rdbuf() << ends;
- char* s = oss.str();
- ::MessageBox(0, s, "OOFILE Error", MB_ICONSTOP);
- oss.rdbuf()->freeze(0); // leave buffer for stream to delete
- #else
- #ifdef _Macintosh
- ostrstream oss;
- #ifdef __MWERKS__
- oss << *(os.rdbuf()) << ends; // they don't define operator<<(streambuf*)
- #else
- oss << os.rdbuf() << ends;
- #endif
- char* s = oss.str();
- macAlertMsg x(s);
- oss.rdbuf()->freeze(0); // leave buffer for stream to delete
- #else
- cout << os.rdbuf();
- cout << "Press Return to continue";
- char c;
- cin.flags(cin.flags() & ~ios::skipws);
- cin >> c;
- #endif
- #endif
- abort();
- }
-
-
- void dbConnect::raise(const char* str)
- {
- #ifdef _Windows
- ::MessageBox(0, str, "OOFILE Error", MB_ICONSTOP);
- #else
- #ifdef _Macintosh
- macAlertMsg x(str);
- #else
- cout << str << endl;
- cout << "Press Return to continue";
- char c;
- cin.flags(cin.flags() & ~ios::skipws);
- cin >> c;
- #endif
- #endif
- abort();
- }
-
-
- #ifdef _Macintosh
- macAlertMsg::macAlertMsg(const char* constMsg)
- {
- Str255 s0, s1, s2, s3;
- s0[0] = s1[0] = s2[0] = s3[0] = 0;
- int lenToNextStart, lenToWordBreak, bitLen;
-
- char* msg = (char*) constMsg; // cast away const as some of the toolbox routines aren't const
- // but *are* safe, so we are preserving the intent
- long msgLen = strlen(msg);
-
- if (msgLen>255) {
- lenToWordBreak = FindWordBreakBefore(255, msg, lenToNextStart);
- msgLen -= (lenToNextStart+1);
- }
- else {
- lenToWordBreak = msgLen;
- msgLen = 0;
- }
-
- memcpy(&s0[1], msg, lenToWordBreak);
- s0[0] = lenToWordBreak;
-
- if (msgLen>0) {
- msg += lenToNextStart;
- bitLen = (msgLen>255) ? 255 : msgLen+1; // point to nil byte at end of string
- lenToWordBreak = FindWordBreakBefore(bitLen, msg, lenToNextStart);
- memcpy(&s1[1], msg, lenToWordBreak);
- s1[0] = lenToWordBreak;
- msgLen -= (lenToNextStart+1);
- }
-
- if (msgLen>0) {
- msg += lenToNextStart;
- bitLen = (msgLen>255) ? 255 : msgLen+1;
- lenToWordBreak = FindWordBreakBefore(bitLen, msg, lenToNextStart);
- memcpy(&s2[1], msg, lenToWordBreak);
- s2[0] = lenToWordBreak;
- msgLen -= (lenToNextStart+1);
- }
-
- if (msgLen>0) {
- msg += lenToNextStart;
- bitLen = (msgLen>255) ? 255 : msgLen+1;
- lenToWordBreak = FindWordBreakBefore(bitLen, msg, lenToNextStart);
- memcpy(&s3[1], msg, lenToWordBreak);
- s3[0] = (lenToNextStart+1);
- }
-
- ParamText(s0, s1, s2, s3);
- StopAlert(kBigErrAlert, nil);
- }
-
-
- int macAlertMsg::FindWordBreakBefore(int bitLen, char* msg, int &lenToNextStart)
- {
- char* scanFrom = msg+bitLen;
- lenToNextStart = bitLen; // as far as we know - we aren't allowed to skip right
- char* endWord = scanFrom; // in case all one word
- if (isspace(*scanFrom)) { // skip blanks we started on
- while ((scanFrom>msg) && isspace(*scanFrom))
- scanFrom--;
- }
- else {
- if (*scanFrom!='\0') { // not pointing at terminating null
- while ((scanFrom>msg) && !isspace(*scanFrom))
- scanFrom--;
- if ((scanFrom>msg)) {
- char* splitWordStart = scanFrom+1;
- while (isspace(*scanFrom) && (scanFrom>msg)) // skip blanks we reached
- scanFrom--;
- if ((scanFrom>msg))
- lenToNextStart = splitWordStart-msg+2;
- }
- }
- }
- return scanFrom-msg+1;
- }
- #endif
-
-
-
- // -------------------------------------------------------
- // d b T a b l e
- // -------------------------------------------------------
- dbTable::dbTable(const char *tableName) :
- mTableName(tableName),
- mFieldCount(0),
- mSortByFieldNum(0),
- mSaveOption(requireExplicit),
- mRelChains(0)
- {
- #ifdef OOF_Debug
- if(dbConnect::sCurrentlyConstructing==0)
- dbConnect::raise(ostrstream() << "dbTable: " << tableName
- << " is defined AFTER the call to newConnection or openConnection, too late to be part of this database connection's schema");
- #endif
- dbTable::sCurrentlyConstructing = this;
- dbConnect::sCurrentlyConstructing->attachTable(this);
- }
-
-
-
- dbTable::~dbTable()
- {
- delete mBackend;
- delete mRelChains;
- }
-
-
- dbTable::dbTable(const dbTable& rhs) :
- mFields(rhs.mFieldCount),
- mTableName(rhs.mTableName),
- mFieldCount(0),
- mSortByFieldNum(rhs.mSortByFieldNum),
- mSaveOption(rhs.mSaveOption),
- mPartRelations(rhs.mPartRelations),
- mRelChains(0)
- {
- mBackend = rhs.mBackend->clone(sCloningWithoutSelection, mFields);
- sCloningWithoutSelection = false;
- sCurrentlyConstructing = this; // so fields that are about to invoke dbField copy constructor point to ME
- }
-
-
- ostream& operator<<(ostream& os, dbTable& tbl)
- {
- tbl.extract(os);
- return os;
- }
-
-
- ostream& operator<<(ostream& os, dbTable* tbl)
- {
- tbl->extract(os);
- return os;
- }
-
-
- istream& operator>>(istream& is, dbTable* tbl)
- {
- tbl->insert(is);
- return is;
- }
-
-
- istream& operator>>(istream& is, dbTable& tbl)
- {
- tbl.insert(is);
- return is;
- }
-
-
- bool dbTable::pointsToBackend(const OOF_tableBackend* rhsBackend) const
- {
- return mBackend==rhsBackend;
- }
-
-
- bool dbTable::canSaveRecord()
- {
- return true;
- }
-
-
- dbTable* dbTable::cloneWithoutSelection()
- {
- sCloningWithoutSelection = true;
- dbTable* ret = new dbTable(*this);
- assert(ret);
- return ret;
- }
-
-
- void dbTable::attachfield(dbField * theField) {
- mFields.append(theField);
- CompleteField(theField);
- }
-
-
- void dbTable::extract(ostream& os)
- {
- // NOTE: mFields.table = the database/selection
- // mFields = ordered list of fields to report
-
- // stPreserveOOFILEcontext(this); - not yet working, doesn't preserve blobs
-
- start(); // start record iteration (vertical)
- unsigned int numFields = mFields.count();
- while (more()) {
- for (unsigned int i=0; i<numFields; i++)
- {
- dbField *theField = (dbField *) mFields[i]; // safe downcast
- os << theField << "\t";
- }
- os << endl;
- next();
- }
- }
-
-
- unsigned long dbTable::insert(istream& is)
- {
- // later will have a dbView::insert() to let us just import into specified fields
-
- const char kFieldSep = '\t'; // later allow user setting of divider chars
- const char kRecSep = '\n';
-
- unsigned long numAdded = 0;
- is.flags(is.flags() & ~ios::skipws); // we look at the white space!
- while (is.good()) {
- bool gotCompleteRecord = false;
- newRecord(); // start record iteration (vertical)
- unsigned int numFields = mFields.count();
- for (unsigned int i=0; i<numFields; i++)
- {
- dbField *theField = (dbField *) mFields[i]; // safe downcast
- if (!theField->insert(is, kFieldSep, kRecSep))
- break; // field failed for some reason, probably partial record
-
- if (is.good()) { // should be at field or rec separator, NOT end of stream
- char c;
- is >> c; // consume record or field separator
- if (c!=kFieldSep) { // most likely is FieldSep, which just loops around
- assert(c==kRecSep); // otherwise should be end of record
- unsigned int expectedFields = (numFields-1);
- if (i==expectedFields)
- gotCompleteRecord = true;
- else {
- gotCompleteRecord = false;
- #ifdef OOF_Debug
- dbConnect::raise(ostrstream() << "dbTable::insert() in table: " << tableName()
- << " has hit a short record of " << i << " out of "
- << expectedFields << " expected fields"
- << " on import record " << numAdded
- );
- #endif
- }
- break; // end of record, short or complete
- }
- } // stream is good
- else {
- #ifdef OOF_Debug
- dbConnect::raise(ostrstream() << "dbTable::insert() in table: " << tableName()
- << " has a problem with import record " << numAdded
- );
- #endif
- break; // error break - problem with stream
- }
- } // loop through fields
-
- if (gotCompleteRecord) {
- numAdded++;
- saveRecord();
- }
- else {
- unloadRecord(); // unload last probably partial record
- break; // ABNORMAL EXIT FROM LOOP
- }
- } // while getting recs
- return numAdded;
- }
-
-
- void dbTable::saveRecord()
- {
- if (mRelChains)
- mRelChains->propagateSave();
- mBackend->saveRecord();
- }
-
-
- void dbTable::deleteRecord()
- {
- if (isRecordLoaded()) {
- if (mRelChains)
- mRelChains->propagateDelete();
- mBackend->deleteRecord();
- }
- }
-
-
- void dbTable::deleteSelection()
- {
- unsigned long numRecs = count();
- for (unsigned long i=0; i<numRecs; i++)
- {
- start();
- deleteRecord();
- }
- selectNone();
- }
-
-
- void dbTable::deleteAll()
- {
- selectAll();
- deleteSelection();
- selectNone();
- }
-
-
- void dbTable::describe(ostream& os)
- {
- if (mTableName.isEmpty())
- os << "Un-named table";
- else
- os << "table: " << mTableName;
- os << " contains fields:" << endl;
- mFields.describe(os);
- }
-
-
- void dbTable::generateTestData(int numRecords, const char* testBuf, unsigned long testBufLen)
- {
- unsigned int numFields = mFields.count();
- for (unsigned int r=0;r<numRecords; r++) {
- newRecord();
- for (unsigned int i=0; i<numFields; i++)
- {
- dbField *theField = (dbField *) mFields[i]; // safe downcast
- theField->generateTestData(testBuf, testBufLen);
- }
- saveRecord();
- }
- }
-
-
- void dbTable:: newRecord()
- {
- ContextChange();
- // for (unsigned int i=0; i<mFields.count(); i++) {
- // dbField* fld = (dbField *) mFields[i]; // safe downcast
- // fld->reset();
- // }
- mBackend->newRecord();
- }
-
-
- void dbTable::ContextChange()
- {
- #ifdef OOF_Debug
- validateDatabaseState();
- #endif
- switch (mSaveOption) {
- case autoOnContextChange :
- saveRecord();
- unloadRecord();
- break;
-
- case requireExplicitAndBuffer :
- mBackend->CacheDirtyBuffer();
- break;
-
- case requireExplicit :
- #ifdef OOF_Debug
- if (isDirty())
- dbConnect::raise(ostrstream() << "ContextChange called on dirty record without saving: "
- << "file name: " << tableName()
- << endl);
- #endif
- unloadRecord();
- break;
-
- default :
- assert(0);
- }
-
- if (mRelChains)
- mRelChains->baseContextHasChanged();
- // NOT YET IMPLEMENTED
- // broadcast message to all other dependants logged
- // via ad-hoc dependency mechanism
- }
-
-
-
- bool dbTable::loadRelatedContextJoiningFromTo(const dbField* f1, const dbField* f2)
- {
- if (mRelChains)
- mRelChains->propagateUnload();
- // bit of redirection here - as a destination table, almost certainly in a relation,
- // the field to passed in is probably a "real" field pointing back to the original table
- // not the cloned destination of a relation. To make sure no side effects can change
- // the original table, retrieve our copy of the field.
- dbField* reallyTo = (dbField*) mFields[f2->fieldNumber()]; // safe downcast
-
- return mBackend->loadRelatedContextJoiningFromTo(f1, reallyTo);
- }
-
-
- void dbTable::sortBy(const dbField& theField)
- {
- mSortByFieldNum = theField.fieldNumber();
- mBackend->sortBy(mSortByFieldNum);
- }
-
-
- void dbTable::unloadRecord()
- {
- if (mRelChains)
- mRelChains->propagateUnload();
- mBackend->unloadRecord();
- }
-
-
- void dbTable::CompleteField(dbField * theField)
- {
- theField->mBackend = mBackend;
- theField->mFieldNumber = mFieldCount++;
- theField->mTable = this;
- }
-
-
-
- // -------------------------------------------------------
- // s t P r e s e r v e O O F I L E c o n t e x t
- // -------------------------------------------------------
- stPreserveOOFILEcontext::stPreserveOOFILEcontext(dbTable* tbl)
- {
- mContext = tbl->createContext();
- mTable = tbl;
- }
-
-
- stPreserveOOFILEcontext::stPreserveOOFILEcontext(dbTable& tbl)
- {
- mContext = tbl.createContext();
- mTable = &tbl;
- }
-
-
- stPreserveOOFILEcontext::~stPreserveOOFILEcontext()
- {
- mTable->returnToContext(mContext);
- }
-
-
-